﻿using System;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Controls;
using System.Collections.Generic;
using System.ComponentModel;
using wi = System.Windows.Interactivity;

namespace Thinktecture.UIAnnotations.Behaviors
{
    public class BusyBehavior : wi.Behavior<Control>
    {

        #region Overrides

        protected override void OnAttached()
        {
            base.OnAttached();
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.DetachBusyIndicator(this.AssociatedObject);
        }

        #endregion

        #region Private Functions
        private bool _busyStateChanging;
        private void AttachBusyIndicator(FrameworkElement element, bool isBusy)
        {
            _busyStateChanging = true;
            this.IsBusy = isBusy;
            _busyStateChanging = false;

            // find the root element
            while (element.Parent != null)
            {
                if (element.Parent is FrameworkElement)
                {
                    element = element.Parent as FrameworkElement;
                }
                else
                    break;
            }

            if (element != null && element is ContentControl)
            {
                // find the root panel
                foreach (var child in LogicalTreeHelper.GetChildren(element))
                {
                    if (typeof(Panel).IsAssignableFrom(child.GetType()))
                    {
                        // add overlay panel
                        var panel = new Canvas();
                        ((Panel)child).Children.Add(panel);
                        panel.Focusable = false;
                        panel.HorizontalAlignment = HorizontalAlignment.Stretch;
                        panel.VerticalAlignment = VerticalAlignment.Stretch;
                        if (child is Grid)
                        {
                            var grid = child as Grid;
                            Grid.SetRow(panel, 0);
                            Grid.SetColumn(panel, 0);
                            if (grid.RowDefinitions.Count > 0)
                                Grid.SetRowSpan(panel, grid.RowDefinitions.Count);
                            if (grid.ColumnDefinitions.Count > 0)
                                Grid.SetColumnSpan(panel, grid.ColumnDefinitions.Count);
                        }

                        // add background
                        var border = new Border
                        {
                            Background = new SolidColorBrush(Color.FromArgb(127, 0, 0, 0)),
                            Opacity = 0d,
                            HorizontalAlignment = HorizontalAlignment.Stretch,
                            VerticalAlignment = VerticalAlignment.Stretch
                        };
                        border.Loaded += this.OnBorderLoaded;
                        panel.Children.Add(border);

                        // add busy indicator
                        var indicator = new BusyIndicator() { Name = "busyIndicator" };
                        panel.Children.Add(indicator);
                        indicator.Focusable = false;
                        indicator.Loaded += this.OnIndicatorLoaded;
                        return;
                    }
                }
            }
        }

        private void OnBorderLoaded(object sender, RoutedEventArgs e)
        {
            var border = sender as Border;
            border.Loaded -= this.OnBorderLoaded;
            border.Width = ((Panel)border.Parent).RenderSize.Width;
            border.Height = ((Panel)border.Parent).RenderSize.Height;
            var anim = new DoubleAnimation(1, new Duration(new TimeSpan(0, 0, 0, 1, 0)));
            border.BeginAnimation(Border.OpacityProperty, anim);
        }

        private void OnIndicatorLoaded(object sender, RoutedEventArgs e)
        {
            var indicator = sender as BusyIndicator;
            indicator.Loaded -= this.OnIndicatorLoaded;
            indicator.SetValue(Canvas.LeftProperty, (((Panel)indicator.Parent).RenderSize.Width - indicator.RenderSize.Width) / 2);
            indicator.SetValue(Canvas.TopProperty, (((Panel)indicator.Parent).RenderSize.Height - indicator.RenderSize.Height) / 2);
            indicator.IsBusy = this.IsBusy;
        }

        private void DetachBusyIndicator(FrameworkElement element)
        {
            var indicator = this.GetBusyIndicator(element);
            if (indicator != null)
            {
                var panel = ((Panel)indicator.Parent).Parent as Panel;
                panel.Children.Remove((UIElement)indicator.Parent);
            }
        }

        private BusyIndicator GetBusyIndicator(FrameworkElement element)
        {
            // find the root element
            while (element.Parent != null)
            {
                if (element.Parent is FrameworkElement)
                {
                    element = element.Parent as FrameworkElement;
                }
                else
                    break;
            }

            if (element != null && element is ContentControl)
            {
                return VisualTreeHelpers.FindDescendent(element, "busyIndicator") as BusyIndicator;
            }
            return null;
        }

        #endregion

        #region IsBusy

        public static readonly DependencyProperty IsBusyProperty =
            DependencyProperty.Register("IsBusy", typeof(bool), typeof(BusyBehavior), new UIPropertyMetadata(false, OnIsBusyPropertyChanged));
        public bool IsBusy { get { return (bool)GetValue(IsBusyProperty); } set { SetValue(IsBusyProperty, value); } }

        private static void OnIsBusyPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((BusyBehavior)sender).OnIsBusyChanged(sender, e);
        }

        private void OnIsBusyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (!_busyStateChanging)
            {
                if ((bool)e.NewValue)
                    this.AttachBusyIndicator(this.AssociatedObject as FrameworkElement, this.IsBusy);
                else
                    this.DetachBusyIndicator(this.AssociatedObject as FrameworkElement);
            }
        }

        #endregion

        #region Attach Attached Property

        public static readonly DependencyProperty AttachProperty =
            DependencyProperty.RegisterAttached("Attach", typeof(bool), typeof(BusyBehavior),
            new PropertyMetadata(false, OnAttachPropertyChanged));

        public static bool GetAttach(UIElement element)
        {
            return (bool)element.GetValue(AttachProperty);
        }

        public static void SetAttach(UIElement element, bool value)
        {
            element.SetValue(AttachProperty, value);
        }

        private static void OnAttachPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            new BusyBehavior().OnAttachChanged(sender, e);
        }

        private void OnAttachChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(sender)) return;
            bool value = (bool)e.NewValue;
            if (value)
            {
                var behavior = GetBehavior<BusyBehavior>(sender);
                if (behavior == null)
                {
                    behavior = this;
                    wi.Interaction.GetBehaviors(sender).Add(behavior);
                }

                behavior.IsBusy = true;
            }
            else
            {
                var behavior = GetBehavior<BusyBehavior>(sender);
                if (behavior != null)
                    behavior.IsBusy = false;
            }
        }
        private static TAction GetBehavior<TAction>(DependencyObject element) where TAction : wi.Behavior
        {
            return (from b in wi.Interaction.GetBehaviors(element)
                    where b.GetType().Equals(typeof(TAction))
                    select b).FirstOrDefault() as TAction;
        }

        #endregion

    }
}
